package habitat;

import game.Constants;
import game.PayOffMatrix;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Vector;



import game.player.Agent;
import game.strategy.Strategy;


public class Habitat {

	/**
	 * Local Referee Agent
	 * 
	 */
	private static final long serialVersionUID = 797276631810418854L;

	// / agents are selected to be moved to another container with probability
	// / based on success
	private static final int AGENT_MOBILITY_MODE_PROBABILISTIC = 0;

	// / agents are selected based on
	private static final int AGENT_MOBILITY_MODE_SUCCESS_RATIO = 1;

	
	// /Agents in the container
	private Vector<Agent> players;

	// / the type of game played among agents
	private int gameID;

	// / A new season starts after a complete tournament ends, )
	private int iteration;

	// the diversity assigned to each habitat
	private double divertsityValue;

	// private FileLogger containerLooger;

	private Random rand;

	private int numberOfAgents;

	private Vector<int[]> intmat;

	private int ID;

	private float totalScore;
	
	private int numOfDefects;
	
	private int numOfCooperates;
	
	private int numOfEncounters;

	private Map<Integer, Agent> id2AgentMap;
	
	private int totalOutDegree;
	
	private Map<Integer,LinkedList<int[]>> edgesConnectedToAgent;

	// private Topology habitat;

	public Habitat(int id, Vector<Agent> agents, Vector<int[]> intmat, double sf) {

		// / get arguments given at creation of LocalRefree Agent
		ID = id;
		players = agents;
		this.intmat = intmat;
		this.divertsityValue = sf;
		// / Initialize random variables
		rand = new Random();
		iteration = 0;
		// setting gameID
		gameID = Constants.GAME_ID;
		// printing the game Pay off
		//System.out.println(PayOffMatrix.getSingleton().toString(gameID));
		id2AgentMap = new HashMap<Integer, Agent>();
		totalOutDegree = 0;
		numberOfAgents = 0;
		numOfCooperates = 0;
		numOfDefects = 0;
		
		edgesConnectedToAgent = new HashMap<Integer, LinkedList<int[]>>();
		for(Iterator<Agent> iterator = players.iterator();iterator.hasNext();){
			Agent a = iterator.next();
			id2AgentMap.put(a.getID(), a);
			edgesConnectedToAgent.put(a.getID(), new LinkedList<int[]>());
			numberOfAgents++;
			if(a.getStrategy() == Strategy.COOPERATE)
				numOfCooperates++;
			else
				numOfDefects++;
		}
		for (Iterator<int[]> iterator = intmat.iterator(); iterator.hasNext();) {
			int[] is = iterator.next();
			java.util.List<int[]> list1 = edgesConnectedToAgent.get(is[0]);
			list1.add(is);
			java.util.List<int[]> list2 = edgesConnectedToAgent.get(is[1]);
			list2.add(is);
			Agent agent1 = id2AgentMap.get(is[0]);
			agent1.outdegree++;
			Agent agent2 = id2AgentMap.get(is[1]);
			agent2.outdegree++;
			totalOutDegree +=2;
			/*System.out.println("Edge : "+is[0]+" -- "+is[1]);
			System.out.println("agent "+agent1.ID+" outdegree :"+agent1.outdegree);
			System.out.println("agent "+agent2.ID+" outdegree :"+agent2.outdegree);*/
		}

	}

	public void addAgent(Agent agent){
		//System.out.println("Habitat.addAgent()");
		if(! players.add(agent))
			return;
		agent.outdegree = 0;
		id2AgentMap.put(agent.getID(), agent);
		edgesConnectedToAgent.put(agent.getID(), new LinkedList<int[]>());
		numberOfAgents++;
		if(agent.getStrategy() == Strategy.COOPERATE)
			numOfCooperates++;
		else
			numOfDefects++;
	}
	public void removeAgent(Agent agent){
		int[][] edges = edgesConnectedToAgent.get(agent.getID()).toArray(new int [0][]);
		for (int i=0;i<edges.length;i++) {
			int[] is = edges[i];
			removeEdge(is); 	
		}
		id2AgentMap.remove(agent.getID());
		players.remove(agent);
		edgesConnectedToAgent.remove(agent.getID());
		numberOfAgents--;
		if(agent.getStrategy() == Strategy.COOPERATE)
			numOfCooperates--;
		else
			numOfDefects--;
	}
	public void removeEdge(int[] edge){
		intmat.remove(edge);
		java.util.List<int[]> list1 = edgesConnectedToAgent.get(edge[0]);
		list1.remove(edge);
		java.util.List<int[]> list2 = edgesConnectedToAgent.get(edge[1]);
		list2.remove(edge);
		Agent agent1 = id2AgentMap.get(edge[0]);
		agent1.outdegree--;
		Agent agent2 = id2AgentMap.get(edge[1]);
		agent2.outdegree--;
		totalOutDegree -=2;
	}
	public void addEdge(int[] is) {
		//System.out.println("Habitat.addEdge()");
		intmat.add(is);
		java.util.List<int[]> list1 = edgesConnectedToAgent.get(is[0]);
		if(list1 ==null)
			System.out.println(" --");
		list1.add(is);
		java.util.List<int[]> list2 = edgesConnectedToAgent.get(is[1]);
		list2.add(is);
		Agent agent1 = id2AgentMap.get(is[0]);
		agent1.outdegree++;
		Agent agent2 = id2AgentMap.get(is[1]);
		agent2.outdegree++;
		totalOutDegree +=2;
		/*System.out.println("Edge : "+is[0]+" -- "+is[1]);
		System.out.println("agent "+agent1.ID+" outdegree :"+agent1.outdegree);
		System.out.println("agent "+agent2.ID+" outdegree :"+agent2.outdegree);*/
	}
	public int getTotalOutDegree() {
		return totalOutDegree;
	}
	
	public int getID() {
		return ID;
	}
	
	public Vector<Agent> getPlayers() {
		return players;
	}

	public double getDivertsityValue() {
		return divertsityValue;
	}

	public int getNumberOfAgents() {
		return numberOfAgents;
	}

	public Vector<int[]> getIntmat() {
		return intmat;
	}

	public float getTotalScore() {
		return totalScore;
	}

	public int getNumOfDefects() {
		return numOfDefects;
	}

	public int getNumOfCooperates() {
		return numOfCooperates;
	}

	private void printSchedule() {
		int i=1;
		if (intmat != null)
			for (Iterator<int[]> iterator = intmat.iterator(); iterator.hasNext();) {
				int[] encounter = iterator.next();
				System.out.println(i + ". Encounter is between agent "
						+ encounter[0] + " agent " + encounter[1]);
				
			}

	}

	public void startTournament() {

		Agent firstAgent;

		Agent secondAgent;

		int firstAgentAction;

		int secondAgentAction;

		iteration++;
		initiliaze();

		// / print schedule

		if (intmat == null || intmat.size() == 0)
			return;

		for (int i = 0; i < numOfEncounters; i++) {
			int[] encounter = intmat.get(i);
			firstAgent = getAgent(encounter[0]);
			secondAgent = getAgent(encounter[1]);
			//System.out.println("scheduleIndex :" + i + " of" + numOfEncounters);
			//System.out.println("Encounter between " + firstAgent.getID()
			//		+ " and " + secondAgent.getID());

			// / getting players' action
			firstAgentAction = firstAgent.play();
			secondAgentAction = secondAgent.play();

			// /evaluate players' actions and update score table
			float score1 = PayOffMatrix.getSingleton().getFirstScore(gameID,
					firstAgentAction, secondAgentAction);
			float score2 = PayOffMatrix.getSingleton().getSecondScore(gameID,
					firstAgentAction, secondAgentAction);
			// rescale the score according to (1+h)*X where X score and h
			// diversity value
			score1 = reScalePayOff(score1);
			score2 = reScalePayOff(score2);

			// update the score of agents
			firstAgent.setScore( firstAgent.getScore() + score1);
			secondAgent.setScore(  secondAgent.getScore() +score2);

			totalScore += score1 + score2;

//			System.out.println(Strategy.STRATEGIES[firstAgent.strategy] +firstAgent.ID +" wins :" + score1 + " units, "
//					+Strategy.STRATEGIES[secondAgent.strategy]+ secondAgent.ID + " wins :" + score2 + " units");
		}

	}

	public float reScalePayOff(float score) {
		return (float) (score * (1.0 + divertsityValue));
	}

	private Agent getAgent(int id) {
		return id2AgentMap.get(id);
	}

	public void printScoreTable() {

		//Simulation.logger.log("\nTotal score of habitat "+getID()+" is : "+totalScore);
		/*for (int i = 0; i < numberOfAgents; i++) {
			Simulation.logger.log("player " + players.get(i).ID
					+ " has score of " + players.get(i).score + "\n");
		}*/
	}

	private void initiliaze() {

		numberOfAgents = players.size();
		numOfEncounters = intmat.size();

		/// Initializing score of agents
		for (int i = 0; i < numberOfAgents; i++) {
			players.get(i).setScore( 0 );
		}
		//System.out.println(" Diversity value for habitat "+getID()+" is : "+divertsityValue);

	}

/*	public Vector<Agent> getLeavingPlayers() {
		// / select agents to be moved
		return selectAgentsToBeMoved();
	}

	private Vector<Agent> selectAgentsToBeMoved() {
		Vector<Agent> selectedPlayers;

		double probablity;
		// / if agent mobility type is AGENT_MOBILITY_MODE_PROBABILISTIC
		// / the selection of agents to be moved is based on probabilities of
		// / agents' success
		// / it generates probabilities based on success and selection of
		// / agents
		if (Constants.AGENT_MOBILITY_MODE == AGENT_MOBILITY_MODE_PROBABILISTIC) {
			selectedPlayers = new Vector<Agent>();
			//double maxscore = totalScore/numberOfAgents;

			for (Iterator<Agent> iterator = players.iterator(); iterator.hasNext();) {
				Agent player = iterator.next();
				//probablity = (1.0 + Math.exp( (player.score - ave)/Constants.K1VALUE) );
				if(player.strategy == Strategy.DEFECT)
					probablity = 1.0 - player.score / ( reScalePayOff( (float) Constants.BVALUE)*((double)player.outdegree) );
				else
					probablity = 1.0 - player.score / ( reScalePayOff( (float) 1.0)*((double)player.outdegree) );
				
				if (rand.nextDouble() < probablity)
					selectedPlayers.add(player);
			}

			return selectedPlayers;
		}
		// / /if agent mobility type is AGENT_MOBILITY_MODE_SUCCESS_RATIO
		// / moving agents are selected whether they is under certain success
		// / ratio
		else if (Constants.AGENT_MOBILITY_MODE == AGENT_MOBILITY_MODE_SUCCESS_RATIO) {
			double ave;
			selectedPlayers = new Vector<Agent>();
			ave = totalScore / (double) numberOfAgents;
			for (Iterator<Agent> iterator = players.iterator(); iterator.hasNext();) {
				Agent player = iterator.next();
				// / if an agent's success is below the creation success ratio
				// / the agent is selected to be moved from the container
				//* Constants.SUCCESS_RATIO_FOR_AGENTS_NOT_BE_MOVED
				if (ave  < player.getScore())
					selectedPlayers.add(player);
			}

		}
		return null;
	}
*/
	
	public Vector<Agent> getConnectedAgents(Agent agent){
		Vector<Agent> connectedAgents = new Vector<Agent>();
		LinkedList<int[]> list = edgesConnectedToAgent.get(agent.getID());
		for (Iterator<int[]> iterator = list.iterator(); iterator.hasNext();) {
			int[] edge = iterator.next();
			if ( agent.getID() == edge[1] )
				connectedAgents.add(id2AgentMap.get(edge[0]));
			else if ( agent.getID() == edge[0] )
				connectedAgents.add(id2AgentMap.get(edge[1]));
			
		}
		return connectedAgents;
	}

	
	
	public List<int[]> getConnectedEdges(Agent agent) {
		LinkedList<int[]> list = null;
		if (edgesConnectedToAgent != null)
			list = edgesConnectedToAgent.get(agent.getID());
		return list;
	}


	public int getIntmatSize() {
		return intmat.size();
	}
	
	public void changeAgentStratgey(Agent agent) {
		
		if (agent.outdegree == 0)
			return;

		Agent pickedAgent = getConnectedAgents(agent).elementAt(
				rand.nextInt(agent.outdegree));

		double prob;
		if(Constants.STRATEGY_UPDATE == 0)
			prob = probabDistr1(agent.getScore(), pickedAgent.getScore());
		else 
			prob = probabDistr2(agent.getScore(), pickedAgent.getScore(), agent.outdegree, pickedAgent.outdegree);

		if (prob > rand.nextDouble()) {
			if (agent.getStrategy() != pickedAgent.getStrategy()) {
				if (agent.getStrategy() == Strategy.COOPERATE) {
					numOfCooperates--;
					numOfDefects++;
				} else {
					numOfCooperates++;
					numOfDefects--;
				}
				agent.setStrategy( pickedAgent.getStrategy() );
			}
		}

	}
	/*
	 * selects <code>n</code> integers between 0 and <code>N-1</code> based on
	 * their probabilities; places the selected integers in <code>sample</code>;
	 * probabilities should be cumulative probabilities
	 */
	/*public void selectProb(int N, double[] probabilities, Integer[] sample,
			int n, Random rand) {
		for (int j = 0; j < n; j++) {
			double val = rand.nextDouble();

			// / do a binary search for the location i such that
			// / probabilities[i - 1] < val <= probabilities[i]
			int i = 0;
			int left = 0;
			int right = N - 1;
			while (right >= left) {
				int middle = (left + right) / 2;
				if (val - probabilities[middle] < 0.0)
					right = middle - 1;
				else if (val - probabilities[middle] > 0.0)
					left = middle + 1;
				else {
					i = middle;
					break;
				}
			}

			if (right < left)
				i = left;

			// / in the case there were neighboring
			// / items with probability 0
			while (i > 0 && probabilities[i - 1] == val)
				i--;

			sample[j] = i;
		}
	}*/

	private double probabDistr1(float scorex, float scorey) {
		return 1.0 / (1.0 + Math.exp((scorex - scorey)
				/ Constants.K1VALUE));
	}
	private double probabDistr2(float scorex, float scorey,int kx,int ky) {
		int kl;
		if(kx>ky)	kl=kx;
		else 	kl=ky;
		
		return  ( (scorey - scorex)/(Constants.BVALUE*kl) );
	}

}
